import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import {{invokerPackage}}.JSON;

/**
 * {{description}}{{^description}}{{classname}}{{/description}}{{#isDeprecated}}
 * @deprecated{{/isDeprecated}}
 */{{#isDeprecated}}
@Deprecated{{/isDeprecated}}{{#description}}
@ApiModel(description = "{{{.}}}"){{/description}}
{{#jackson}}
@JsonPropertyOrder({
{{#vars}}
  {{classname}}.JSON_PROPERTY_{{nameInSnakeCase}}{{^-last}},{{/-last}}
{{/vars}}
})
{{#isClassnameSanitized}}
@JsonTypeName("{{name}}")
{{/isClassnameSanitized}}
{{/jackson}}
{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}}
{{#vendorExtensions.x-class-extra-annotation}}
{{{vendorExtensions.x-class-extra-annotation}}}
{{/vendorExtensions.x-class-extra-annotation}}
public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtensions.x-implements}}{{#-first}}implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{#-last}} {{/-last}}{{/vendorExtensions.x-implements}}{
{{#serializableModel}}
  private static final long serialVersionUID = 1L;

{{/serializableModel}}
  {{#vars}}
    {{#isEnum}}
    {{^isContainer}}
{{>modelInnerEnum}}
    {{/isContainer}}
    {{#isContainer}}
    {{#mostInnerItems}}
{{>modelInnerEnum}}
    {{/mostInnerItems}}
    {{/isContainer}}
    {{/isEnum}}
  {{#gson}}
  public static final String SERIALIZED_NAME_{{nameInSnakeCase}} = "{{baseName}}";
  {{/gson}}
  {{#jackson}}
  public static final String JSON_PROPERTY_{{nameInSnakeCase}} = "{{baseName}}";
  {{/jackson}}
  {{#withXml}}
  {{#isXmlAttribute}}
  @XmlAttribute(name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}")
  {{/isXmlAttribute}}
  {{^isXmlAttribute}}
    {{^isContainer}}
  @XmlElement({{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}")
    {{/isContainer}}
    {{#isContainer}}
  // Is a container wrapped={{isXmlWrapped}}
      {{#items}}
  // items.name={{name}} items.baseName={{baseName}} items.xmlName={{xmlName}} items.xmlNamespace={{xmlNamespace}}
  // items.example={{example}} items.type={{dataType}}
  @XmlElement({{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}")
      {{/items}}
      {{#isXmlWrapped}}
  @XmlElementWrapper({{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}")
      {{/isXmlWrapped}}
    {{/isContainer}}
  {{/isXmlAttribute}}
  {{/withXml}}
  {{#gson}}
  @SerializedName(SERIALIZED_NAME_{{nameInSnakeCase}})
  {{/gson}}
  {{#vendorExtensions.x-field-extra-annotation}}
  {{{vendorExtensions.x-field-extra-annotation}}}
  {{/vendorExtensions.x-field-extra-annotation}}
  {{#vendorExtensions.x-is-jackson-optional-nullable}}
  {{#isContainer}}
  private JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>undefined();
  {{/isContainer}}
  {{^isContainer}}
  private JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>{{#defaultValue}}of({{{.}}}){{/defaultValue}}{{^defaultValue}}undefined(){{/defaultValue}};
  {{/isContainer}}
  {{/vendorExtensions.x-is-jackson-optional-nullable}}
  {{^vendorExtensions.x-is-jackson-optional-nullable}}
  {{#isContainer}}
  private {{{datatypeWithEnum}}} {{name}}{{#required}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/required}}{{^required}} = null{{/required}};
  {{/isContainer}}
  {{^isContainer}}
  {{#isDiscriminator}}protected{{/isDiscriminator}}{{^isDiscriminator}}private{{/isDiscriminator}} {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}};
  {{/isContainer}}
  {{/vendorExtensions.x-is-jackson-optional-nullable}}

  {{/vars}}
  public {{classname}}() {
    {{#parent}}
    {{#parcelableModel}}
    super();
    {{/parcelableModel}}
    {{/parent}}
    {{#gson}}
    {{#discriminator}}
    {{^discriminator.isEnum}}
    this.{{{discriminatorName}}} = this.getClass().getSimpleName();
    {{/discriminator.isEnum}}
    {{/discriminator}}
    {{/gson}}
  }
  {{#vendorExtensions.x-has-readonly-properties}}
  {{^withXml}}

  {{#jsonb}}@JsonbCreator{{/jsonb}}{{#jackson}}@JsonCreator{{/jackson}}
  public {{classname}}(
  {{#readOnlyVars}}
    {{#jsonb}}@JsonbProperty("{{baseName}}"){{/jsonb}}{{#jackson}}@JsonProperty(JSON_PROPERTY_{{nameInSnakeCase}}){{/jackson}} {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}}
  {{/readOnlyVars}}
  ) {
    this();
  {{#readOnlyVars}}
    this.{{name}} = {{name}};
  {{/readOnlyVars}}
  }
  {{/withXml}}
  {{/vendorExtensions.x-has-readonly-properties}}
  {{#vars}}

  {{^isReadOnly}}
  public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) {
{{> strip_required_string_validation}}
{{> minimum_validation_partial}}
{{> maximum_validation_partial}}
{{> min_length_validation_partial}}
    {{#vendorExtensions.x-is-jackson-optional-nullable}}this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{name}});{{/vendorExtensions.x-is-jackson-optional-nullable}}
    {{^vendorExtensions.x-is-jackson-optional-nullable}}this.{{name}} = {{name}};{{/vendorExtensions.x-is-jackson-optional-nullable}}
    return this;
  }
  {{#isNumber}}

  public {{classname}} {{name}}(Integer {{name}}) {
{{> strip_required_string_validation}}
{{> minimum_validation_partial}}
{{> maximum_validation_partial}}
{{> min_length_validation_partial}}
    {{#vendorExtensions.x-is-jackson-optional-nullable}}this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{name}}.doubleValue());{{/vendorExtensions.x-is-jackson-optional-nullable}}
    {{^vendorExtensions.x-is-jackson-optional-nullable}}this.{{name}} = {{name}}.doubleValue();{{/vendorExtensions.x-is-jackson-optional-nullable}}
    return this;
  }
  {{/isNumber}}
  {{#isArray}}

  public {{classname}} add{{nameInCamelCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) {
    {{#vendorExtensions.x-is-jackson-optional-nullable}}
    if (this.{{name}} == null || !this.{{name}}.isPresent()) {
      this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{{defaultValue}}});
    }
    try {
      this.{{name}}.get().add({{name}}Item);
    } catch (java.util.NoSuchElementException e) {
      // this can never happen, as we make sure above that the value is present
    }
    return this;
    {{/vendorExtensions.x-is-jackson-optional-nullable}}
    {{^vendorExtensions.x-is-jackson-optional-nullable}}
    {{^required}}
    if (this.{{name}} == null) {
      this.{{name}} = {{{defaultValue}}};
    }
    {{/required}}
    this.{{name}}.add({{name}}Item);
    return this;
    {{/vendorExtensions.x-is-jackson-optional-nullable}}
  }
  {{/isArray}}
  {{#isMap}}

  public {{classname}} put{{nameInCamelCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) {
    {{#vendorExtensions.x-is-jackson-optional-nullable}}
    if (this.{{name}} == null || !this.{{name}}.isPresent()) {
      this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{{defaultValue}}});
    }
    try {
      this.{{name}}.get().put(key, {{name}}Item);
    } catch (java.util.NoSuchElementException e) {
      // this can never happen, as we make sure above that the value is present
    }
    return this;
    {{/vendorExtensions.x-is-jackson-optional-nullable}}
    {{^vendorExtensions.x-is-jackson-optional-nullable}}
    {{^required}}
    if (this.{{name}} == null) {
      this.{{name}} = {{{defaultValue}}};
    }
    {{/required}}
    this.{{name}}.put(key, {{name}}Item);
    return this;
    {{/vendorExtensions.x-is-jackson-optional-nullable}}
  }
  {{/isMap}}

  {{/isReadOnly}}
   /**
  {{#description}}
   * {{.}}
  {{/description}}
  {{^description}}
   * Get {{name}}
  {{/description}}
  {{#minimum}}
   * minimum: {{.}}
  {{/minimum}}
  {{#maximum}}
   * maximum: {{.}}
  {{/maximum}}
   * @return {{name}}
   {{#deprecated}}
   * @deprecated
   {{/deprecated}}
  **/
{{#deprecated}}
  @Deprecated
{{/deprecated}}
{{#required}}
{{#isNullable}}
  @javax.annotation.Nullable
{{/isNullable}}
{{^isNullable}}
  @javax.annotation.Nonnull
{{/isNullable}}
{{/required}}
{{^required}}
  @javax.annotation.Nullable
{{/required}}
{{#jsonb}}
  @JsonbProperty("{{baseName}}")
{{/jsonb}}
{{#useBeanValidation}}{{>beanValidation}}{{/useBeanValidation}}  @ApiModelProperty({{#example}}example = "{{{.}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}value = "{{{description}}}")
{{#vendorExtensions.x-extra-annotation}}
  {{{vendorExtensions.x-extra-annotation}}}
{{/vendorExtensions.x-extra-annotation}}
{{#vendorExtensions.x-is-jackson-optional-nullable}}
  {{!unannotated, Jackson would pick this up automatically and add it *in addition* to the _JsonNullable getter field}}
  @JsonIgnore
{{/vendorExtensions.x-is-jackson-optional-nullable}}
{{^vendorExtensions.x-is-jackson-optional-nullable}}{{#jackson}}{{> jackson_annotations}}{{/jackson}}{{/vendorExtensions.x-is-jackson-optional-nullable}}
  public {{{datatypeWithEnum}}} {{getter}}() {
    {{#vendorExtensions.x-is-jackson-optional-nullable}}
    {{#isReadOnly}}{{! A readonly attribute doesn't have setter => jackson will set null directly if explicitly returned by API, so make sure we have an empty JsonNullable}}
    if ({{name}} == null) {
      {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>{{#defaultValue}}of({{{.}}}){{/defaultValue}}{{^defaultValue}}undefined(){{/defaultValue}};
    }
    {{/isReadOnly}}
    return {{name}}.orElse(null);
    {{/vendorExtensions.x-is-jackson-optional-nullable}}
    {{^vendorExtensions.x-is-jackson-optional-nullable}}
    return {{name}};
    {{/vendorExtensions.x-is-jackson-optional-nullable}}
  }

  {{#vendorExtensions.x-is-jackson-optional-nullable}}
{{> jackson_annotations}}
  public JsonNullable<{{{datatypeWithEnum}}}> {{getter}}_JsonNullable() {
    return {{name}};
  }
  {{/vendorExtensions.x-is-jackson-optional-nullable}}{{#vendorExtensions.x-is-jackson-optional-nullable}}
  @JsonProperty(JSON_PROPERTY_{{nameInSnakeCase}})
  {{#isReadOnly}}private{{/isReadOnly}}{{^isReadOnly}}public{{/isReadOnly}} void {{setter}}_JsonNullable(JsonNullable<{{{datatypeWithEnum}}}> {{name}}) {
    {{! For getters/setters that have name differing from attribute name, we must include setter (albeit private) for jackson to be able to set the attribute}}
    this.{{name}} = {{name}};
  }
  {{/vendorExtensions.x-is-jackson-optional-nullable}}

  {{^isReadOnly}}
{{#vendorExtensions.x-setter-extra-annotation}}  {{{vendorExtensions.x-setter-extra-annotation}}}
{{/vendorExtensions.x-setter-extra-annotation}}{{#jackson}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{> jackson_annotations}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}}  public void {{setter}}({{{datatypeWithEnum}}} {{name}}) {
    {{#vendorExtensions.x-is-jackson-optional-nullable}}
    this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{name}});
    {{/vendorExtensions.x-is-jackson-optional-nullable}}
    {{^vendorExtensions.x-is-jackson-optional-nullable}}
{{> strip_required_string_validation}}
{{> minimum_validation_partial}}
{{> maximum_validation_partial}}
{{> min_length_validation_partial}}
    this.{{name}} = {{name}};
    {{/vendorExtensions.x-is-jackson-optional-nullable}}
  }
  {{/isReadOnly}}

  {{/vars}}
{{>libraries/okhttp-gson/additional_properties}}

  @Override
  public boolean equals(Object o) {
  {{#useReflectionEqualsHashCode}}
    return EqualsBuilder.reflectionEquals(this, o, false, null, true);
  {{/useReflectionEqualsHashCode}}
  {{^useReflectionEqualsHashCode}}
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }{{#hasVars}}
    {{classname}} {{classVarName}} = ({{classname}}) o;
    return {{#vars}}{{#vendorExtensions.x-is-jackson-optional-nullable}}equalsNullable(this.{{name}}, {{classVarName}}.{{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{#isByteArray}}Arrays{{/isByteArray}}{{^isByteArray}}Objects{{/isByteArray}}.equals(this.{{name}}, {{classVarName}}.{{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^-last}} &&
        {{/-last}}{{/vars}}{{#isAdditionalPropertiesTrue}}&&
        Objects.equals(this.additionalProperties, {{classVarName}}.additionalProperties){{/isAdditionalPropertiesTrue}}{{#parent}} &&
        super.equals(o){{/parent}};{{/hasVars}}{{^hasVars}}
    return {{#parent}}super.equals(o){{/parent}}{{^parent}}true{{/parent}};{{/hasVars}}
  {{/useReflectionEqualsHashCode}}
  }{{#vendorExtensions.x-jackson-optional-nullable-helpers}}

  private static <T> boolean equalsNullable(JsonNullable<T> a, JsonNullable<T> b) {
    return a == b || (a != null && b != null && a.isPresent() && b.isPresent() && Objects.deepEquals(a.get(), b.get()));
  }{{/vendorExtensions.x-jackson-optional-nullable-helpers}}

  @Override
  public int hashCode() {
  {{#useReflectionEqualsHashCode}}
    return HashCodeBuilder.reflectionHashCode(this);
  {{/useReflectionEqualsHashCode}}
  {{^useReflectionEqualsHashCode}}
    return Objects.hash({{#vars}}{{#vendorExtensions.x-is-jackson-optional-nullable}}hashCodeNullable({{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{^isByteArray}}{{name}}{{/isByteArray}}{{#isByteArray}}Arrays.hashCode({{name}}){{/isByteArray}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{^-last}}, {{/-last}}{{/vars}}{{#parent}}{{#hasVars}}, {{/hasVars}}super.hashCode(){{/parent}}{{#isAdditionalPropertiesTrue}}{{#hasVars}}, {{/hasVars}}{{^hasVars}}{{#parent}}, {{/parent}}{{/hasVars}}additionalProperties{{/isAdditionalPropertiesTrue}});
  {{/useReflectionEqualsHashCode}}
  }{{#vendorExtensions.x-jackson-optional-nullable-helpers}}

  private static <T> int hashCodeNullable(JsonNullable<T> a) {
    if (a == null) {
      return 1;
    }
    return a.isPresent() ? Arrays.deepHashCode(new Object[]{a.get()}) : 31;
  }{{/vendorExtensions.x-jackson-optional-nullable-helpers}}

  @Override
  public String toString() {
{{#toStringReturnsJson}}
    return toJson();
{{/toStringReturnsJson}}
{{^toStringReturnsJson}}
    StringBuilder sb = new StringBuilder();
    sb.append("class {{classname}} {\n");
    {{#parent}}
    sb.append("    ").append(toIndentedString(super.toString())).append("\n");
    {{/parent}}
    {{#vars}}
    sb.append("    {{name}}: ").append(toIndentedString({{name}})).append("\n");
    {{/vars}}
{{#isAdditionalPropertiesTrue}}
    sb.append("    additionalProperties: ").append(toIndentedString(additionalProperties)).append("\n");
{{/isAdditionalPropertiesTrue}}
    sb.append("}");
    return sb.toString();
{{/toStringReturnsJson}}
  }

  /**
   * Convert the given object to string with each line indented by 4 spaces
   * (except the first line).
   */
  private{{#jsonb}} static{{/jsonb}} String toIndentedString(Object o) {
    if (o == null) {
      return "null";
    }
    return o.toString().replace("\n", "\n    ");
  }

{{#parcelableModel}}

  public void writeToParcel(Parcel out, int flags) {
{{#model}}
{{#isArray}}
    out.writeList(this);
{{/isArray}}
{{^isArray}}
{{#parent}}
    super.writeToParcel(out, flags);
{{/parent}}
{{#vars}}
    out.writeValue({{name}});
{{/vars}}
{{/isArray}}
{{/model}}
  }

  {{classname}}(Parcel in) {
{{#isArray}}
    in.readTypedList(this, {{arrayModelType}}.CREATOR);
{{/isArray}}
{{^isArray}}
{{#parent}}
    super(in);
{{/parent}}
{{#vars}}
{{#isPrimitiveType}}
    {{name}} = ({{{datatypeWithEnum}}})in.readValue(null);
{{/isPrimitiveType}}
{{^isPrimitiveType}}
    {{name}} = ({{{datatypeWithEnum}}})in.readValue({{complexType}}.class.getClassLoader());
{{/isPrimitiveType}}
{{/vars}}
{{/isArray}}
  }

  public int describeContents() {
    return 0;
  }

  public static final Parcelable.Creator<{{classname}}> CREATOR = new Parcelable.Creator<{{classname}}>() {
    public {{classname}} createFromParcel(Parcel in) {
{{#model}}
{{#isArray}}
      {{classname}} result = new {{classname}}();
      result.addAll(in.readArrayList({{arrayModelType}}.class.getClassLoader()));
      return result;
{{/isArray}}
{{^isArray}}
      return new {{classname}}(in);
{{/isArray}}
{{/model}}
    }
    public {{classname}}[] newArray(int size) {
      return new {{classname}}[size];
    }
  };
{{/parcelableModel}}

  public static HashSet<String> openapiFields;
  public static HashSet<String> openapiRequiredFields;

  static {
    // a set of all properties/fields (JSON key names)
    openapiFields = new HashSet<String>();
    {{#allVars}}
    openapiFields.add("{{baseName}}");
    {{/allVars}}

    // a set of required properties/fields (JSON key names)
    openapiRequiredFields = new HashSet<String>();
    {{#requiredVars}}
    openapiRequiredFields.add("{{baseName}}");
    {{/requiredVars}}
  }

 /**
  * Validates the JSON Object and throws an exception if issues found
  *
  * @param jsonObj JSON Object
  * @throws IOException if the JSON Object is invalid with respect to {{classname}}
  */
  public static void validateJsonObject(JsonObject jsonObj) throws IOException {
      if (jsonObj == null) {
        if (!{{classname}}.openapiRequiredFields.isEmpty()) { // has required fields but JSON object is null
          throw new IllegalArgumentException(String.format("The required field(s) %s in {{{classname}}} is not found in the empty JSON string", {{classname}}.openapiRequiredFields.toString()));
        }
      }
      {{^hasChildren}}
      {{^isAdditionalPropertiesTrue}}

      Set<Entry<String, JsonElement>> entries = jsonObj.entrySet();
      // check to see if the JSON string contains additional fields
      for (Entry<String, JsonElement> entry : entries) {
        if (!{{classname}}.openapiFields.contains(entry.getKey())) {
          throw new IllegalArgumentException(String.format("The field `%s` in the JSON string is not defined in the `{{classname}}` properties. JSON: %s", entry.getKey(), jsonObj.toString()));
        }
      }
      {{/isAdditionalPropertiesTrue}}
      {{#requiredVars}}
      {{#-first}}

      // check to make sure all required properties/fields are present in the JSON string
      for (String requiredField : {{classname}}.openapiRequiredFields) {
        if (jsonObj.get(requiredField) == null) {
          throw new IllegalArgumentException(String.format("The required field `%s` is not found in the JSON string: %s", requiredField, jsonObj.toString()));
        }
      }
      {{/-first}}
      {{/requiredVars}}
      {{/hasChildren}}
      {{^discriminator}}
      {{#vars}}
      {{#isArray}}
      {{#items.isModel}}
      {{#required}}
      // ensure the json data is an array
      if (!jsonObj.get("{{{baseName}}}").isJsonArray()) {
        throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString()));
      }

      JsonArray jsonArray{{name}} = jsonObj.getAsJsonArray("{{{baseName}}}");
      // validate the required field `{{{baseName}}}` (array)
      for (int i = 0; i < jsonArray{{name}}.size(); i++) {
        {{{items.dataType}}}.validateJsonObject(jsonArray{{name}}.get(i).getAsJsonObject());
      };
      {{/required}}
      {{^required}}
      if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) {
        JsonArray jsonArray{{name}} = jsonObj.getAsJsonArray("{{{baseName}}}");
        if (jsonArray{{name}} != null) {
          // ensure the json data is an array
          if (!jsonObj.get("{{{baseName}}}").isJsonArray()) {
            throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString()));
          }

          // validate the optional field `{{{baseName}}}` (array)
          for (int i = 0; i < jsonArray{{name}}.size(); i++) {
            {{{items.dataType}}}.validateJsonObject(jsonArray{{name}}.get(i).getAsJsonObject());
          };
        }
      }
      {{/required}}
      {{/items.isModel}}
      {{^items.isModel}}
      {{^required}}
      {{#isNullable}}
      // ensure the optional json data is an array if present (nullable)
      if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull() && !jsonObj.get("{{{baseName}}}").isJsonArray()) {
        throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string or null but got `%s`", jsonObj.get("{{{baseName}}}").toString()));
      }
      {{/isNullable}}
      {{^isNullable}}
      // ensure the optional json data is an array if present
      if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonArray()) {
        throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString()));
      }
      {{/isNullable}}
      {{/required}}
      {{#required}}
      // ensure the required json array is present
      if (jsonObj.get("{{{baseName}}}") == null) {
        throw new IllegalArgumentException("Expected the field `linkedContent` to be an array in the JSON string but got `null`");
      } else if (!jsonObj.get("{{{baseName}}}").isJsonArray()) {
        throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString()));
      }
      {{/required}}
      {{/items.isModel}}
      {{/isArray}}
      {{^isContainer}}
      {{#isString}}
      if ({{#isNullable}}!jsonObj.get("{{{baseName}}}").isJsonNull() && {{/isNullable}}{{^required}}(jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) && {{/required}}!jsonObj.get("{{{baseName}}}").isJsonPrimitive()) {
        throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be a primitive type in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString()));
      }
      {{/isString}}
      {{#isModel}}
      {{^isNestedPrimitive}}
      {{#required}}
      // validate the required field `{{{baseName}}}`
      {{#isNullable}}
      if (!jsonObj.get("{{{baseName}}}").isJsonNull()) {
        {{{dataType}}}.validateJsonObject(jsonObj.getAsJsonObject("{{{baseName}}}"));
      }
      {{/isNullable}}
      {{^isNullable}}
      {{{dataType}}}.validateJsonObject(jsonObj.getAsJsonObject("{{{baseName}}}"));
      {{/isNullable}}
      {{/required}}
      {{^required}}
      // validate the optional field `{{{baseName}}}`
      if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) {
        {{{dataType}}}.validateJsonObject(jsonObj.getAsJsonObject("{{{baseName}}}"));
      }
      {{/required}}
      {{/isNestedPrimitive}}
      {{/isModel}}
      {{/isContainer}}
      {{/vars}}
      {{/discriminator}}
      {{#hasChildren}}
      {{#discriminator}}

      String discriminatorValue = jsonObj.get("{{{propertyBaseName}}}").getAsString();
      switch (discriminatorValue) {
      {{#mappedModels}}
        case "{{mappingName}}":
          {{modelName}}.validateJsonObject(jsonObj);
          break;
      {{/mappedModels}}
        default: 
          throw new IllegalArgumentException(String.format("The value of the `{{{propertyBaseName}}}` field `%s` does not match any key defined in the discriminator's mapping.", discriminatorValue));
      }
      {{/discriminator}}
      {{/hasChildren}}
  }

{{^hasChildren}}
  public static class CustomTypeAdapterFactory implements TypeAdapterFactory {
    @SuppressWarnings("unchecked")
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
       if (!{{classname}}.class.isAssignableFrom(type.getRawType())) {
         return null; // this class only serializes '{{classname}}' and its subtypes
       }
       final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
       final TypeAdapter<{{classname}}> thisAdapter
                        = gson.getDelegateAdapter(this, TypeToken.get({{classname}}.class));

       return (TypeAdapter<T>) new TypeAdapter<{{classname}}>() {
           @Override
           public void write(JsonWriter out, {{classname}} value) throws IOException {
             JsonObject obj = thisAdapter.toJsonTree(value).getAsJsonObject();
             {{#isAdditionalPropertiesTrue}}
             obj.remove("additionalProperties");
             // serialize additonal properties
             if (value.getAdditionalProperties() != null) {
               for (Map.Entry<String, Object> entry : value.getAdditionalProperties().entrySet()) {
                 if (entry.getValue() instanceof String)
                   obj.addProperty(entry.getKey(), (String) entry.getValue());
                 else if (entry.getValue() instanceof Number)
                   obj.addProperty(entry.getKey(), (Number) entry.getValue());
                 else if (entry.getValue() instanceof Boolean)
                   obj.addProperty(entry.getKey(), (Boolean) entry.getValue());
                 else if (entry.getValue() instanceof Character)
                   obj.addProperty(entry.getKey(), (Character) entry.getValue());
                 else if (entry.getValue() == null) {
                   obj.addProperty(entry.getKey(), (String) null);
                 } else {
                   obj.add(entry.getKey(), gson.toJsonTree(entry.getValue()).getAsJsonObject());
                 }
               }
             }
             {{/isAdditionalPropertiesTrue}}
             elementAdapter.write(out, obj);
           }

           @Override
           public {{classname}} read(JsonReader in) throws IOException {
             JsonObject jsonObj = elementAdapter.read(in).getAsJsonObject();
             validateJsonObject(jsonObj);
             {{#isAdditionalPropertiesTrue}}
             // store additional fields in the deserialized instance
             {{classname}} instance = thisAdapter.fromJsonTree(jsonObj);
             for (Map.Entry<String, JsonElement> entry : jsonObj.entrySet()) {
               if (!openapiFields.contains(entry.getKey())) {
                 if (entry.getValue().isJsonPrimitive()) { // primitive type
                   if (entry.getValue().getAsJsonPrimitive().isString())
                     instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsString());
                   else if (entry.getValue().getAsJsonPrimitive().isNumber())
                     instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsNumber());
                   else if (entry.getValue().getAsJsonPrimitive().isBoolean())
                     instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsBoolean());
                   else
                     throw new IllegalArgumentException(String.format("The field `%s` has unknown primitive type. Value: %s", entry.getKey(), entry.getValue().toString()));
                 } else if (entry.getValue().isJsonArray()) {
                     instance.putAdditionalProperty(entry.getKey(), gson.fromJson(entry.getValue(), List.class));
                 } else { // JSON object
                     instance.putAdditionalProperty(entry.getKey(), gson.fromJson(entry.getValue(), HashMap.class));
                 }
               }
             }
             return instance;
             {{/isAdditionalPropertiesTrue}}
             {{^isAdditionalPropertiesTrue}}
             return thisAdapter.fromJsonTree(jsonObj);
             {{/isAdditionalPropertiesTrue}}
           }

       }.nullSafe();
    }
  }
{{/hasChildren}}

 /**
  * Create an instance of {{classname}} given an JSON string
  *
  * @param jsonString JSON string
  * @return An instance of {{classname}}
  * @throws IOException if the JSON string is invalid with respect to {{classname}}
  */
  public static {{{classname}}} fromJson(String jsonString) throws IOException {
    return JSON.getGson().fromJson(jsonString, {{{classname}}}.class);
  }

 /**
  * Convert an instance of {{classname}} to an JSON string
  *
  * @return JSON string
  */
  public String toJson() {
    return JSON.getGson().toJson(this);
  }
}
